package com.example.mq.mqclient;

import com.example.mq.common.BinaryTool;
import com.example.mq.common.MqException;
import com.example.mq.common.communication.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class Connection {

    private Socket socket;
    private InputStream inputStream;
    private OutputStream outputStream;
    private DataOutputStream dataOutputStream;
    private DataInputStream dataInputStream;
    private ExecutorService callbackPool;
    //保存当前连接里面有哪些channel
    private ConcurrentHashMap<String,Channel> channelMap;

    public Connection(String host, int port) throws IOException {
        socket = new Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);
        channelMap = new ConcurrentHashMap<>();
        callbackPool = Executors.newFixedThreadPool(4);
        // 创建一个扫描线程, 由这个线程负责不停的从 socket 中读取响应数据. 把这个响应数据再交给对应的 channel 负责处理.
        Thread t = new Thread(() -> {
                    try {
                        while (!socket.isClosed()) {
                            Response response = readResponse();
                            dispatchResponse(response);
                        }
            } catch (SocketException | EOFException e) {
                // 连接正常断开的. 此时这个异常直接忽略.
                close();
                e.printStackTrace();
                log.info("[Connection] 连接正常断开!");
            } catch (IOException | ClassNotFoundException | MqException e) {
                    log.info("[Connection] 连接异常断开!");
                    e.printStackTrace();
            }
        });
        t.start();
    }

    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if (n != response.getLength()) {
            throw new IOException("读取的响应数据不完整! n: " + n);
        }
        response.setPayload(payload);
        log.info("[Connection] 收到响应! type=" + response.getType() + ", length=" + response.getLength());
        return response;
    }


    //当前类用来判断当前接收到的响应是正常的操作消息队列的操作（0x1-0xb）还是服务器推送消息的（0xc）
    private void dispatchResponse(Response response) throws IOException, ClassNotFoundException {
        if(response.getType() == 0xc) {
            SubScribeReturns scribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            Channel channel = channelMap.get(scribeReturns.getConsumerTag());
            if(channel == null) {
                throw new IOException("[Connection] 当前要找的channel不存在 channelId: " + scribeReturns.getConsumerTag());
            }
            //执行channel内部对象的回调（交给线程池来执行）
            callbackPool.submit(() -> {
               try {
                   channel.getConsumer().handleDelivery(scribeReturns.getConsumerTag(),scribeReturns.getBasicProperties(),
                           scribeReturns.getBody());
               }catch (Exception e) {
                   e.printStackTrace();
                   log.info("[Connection] 执行回调过程中出现错误");
               }
            });
        }else {
            //普通操作消息队列
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if(channel == null) {
                throw new IOException("[Connection] 当前要找的channel不存在 channelId: " + basicReturns.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

    //关闭所有连接
    public void close() {
        try {
            callbackPool.shutdownNow();
            inputStream.close();
            outputStream.close();
            socket.close();
            channelMap.clear();
        } catch (IOException e) {
            e.printStackTrace();
            log.info("[Connection] 连接释放失败");
        }
    }
    //通过该方法在当前Connection中创建一个channel
    public Channel createChannel() throws IOException {
        //使用UUID生成唯一的身份标识
        String channelId = "C-" + UUID.randomUUID();
        Channel channel = new Channel(channelId,this);
        //通知一下服务器，向服务器发送请求
        channelMap.put(channelId,channel);
        boolean ok = channel.createChannel();
        if(!ok) {
            channelMap.remove(channelId);
            throw new IOException("[Connection] 创建channel失败");
        }
        return channel;
    }

    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        log.info("[Connection] 发送请求 type: " + request.getType() + ",length: " + request.getLength());
    }
}
